﻿using System;
using Ewk.Math.ComputationTypes;

namespace Ewk.Math.Algebra.Algorithms.Matrices.Determinant
{
    /// <summary>
    /// Provides a way to compute the determinant of a matrix by using Laplace's formula.
    /// </summary>
    /// <typeparam name="T">The <see cref="Type"/> of the internal value of the elements of the matrix.</typeparam>
    class LaplaceDeterminantFormula<T> : IDeterminantComputer<T>
        where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        #region Implementation of IDeterminantComputer<T>

        /// <summary>
        /// Computes the determinant of the specified <see cref="Matrix{T}"/>.
        /// </summary>
        /// <param name="matrix">The <see cref="Matrix{T}"/> to calculate the determinant from.</param>
        /// <returns>A <see cref="Computable{T}"/> that represents the determinant of the specified <see cref="Matrix{T}"/>.</returns>
        public Computable<T> ComputeDeterminant(Matrix<T> matrix)
        {
            if (matrix == null) return null;
            if (!matrix.IsSquared)
            {
                return Computable<T>.Factory.Zero;
            }

            if (matrix.RowCount == 1)
            {
                return matrix[0, 0];
            }

            var result = Computable<T>.Factory.Zero;

            // TODO: Find the row or the column with the most zero entries.
            var column = matrix.GetColumnContainingTheMostZeroEntries();
            for (var row = 0; row < matrix.RowCount; row++)
            {
                var cofactor = matrix[row, column];
                if (cofactor == Computable<T>.Factory.Zero)
                {
                    continue;
                }

                var minorMatrix = Matrix<T>.Minor(matrix, row, 0);
                var minorDeterminant = cofactor * ComputeDeterminant(minorMatrix);

                var sign = System.Math.Pow(-1, row + column);
                result = sign > 0
                             ? result + minorDeterminant
                             : result - minorDeterminant;
            }

            return result;
        }

        #endregion
    }
}